/*
 * Copyright 2021 Palantir Technologies, Inc. All rights reserved.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @fileoverview Generates SVG React components for each icon.
 * N.B. we expect ../src/generated/ to contain SVG definitions of all the icons already
 * generated by fantasticon (in other words, run ./generate-icon-fonts.js first).
 */

// @ts-check

import { pascalCase } from "change-case";
import Handlebars from "handlebars";
import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { join, resolve } from "node:path";
import { parse } from "svg-parser";

import { generatedComponentsDir, generatedSrcDir, ICON_RASTER_SCALING_FACTOR, ICON_SIZES } from "./common.mjs";

Handlebars.registerHelper("pascalCase", iconName => pascalCase(iconName));

/**
 * Notes on icon component template implementation:
 *
 * The components rendered by this template (`<AddClip>`, `<Calendar>`, etc.) rely on a centered scale `transform` to
 * display their SVG paths correctly.
 *
 * In this template, the `<path>` element applies `transform-origin` using the `style` attribute rather than
 * `transformOrigin`. Although `trasformOrigin` was added as a supported SVG attribute to React in 2023,
 * it is still difficult to use without compile-time and/or runtime errors, see:
 *  - https://github.com/facebook/react/pull/26130
 *  - https://github.com/palantir/blueprint/issues/6591
 */
const iconComponentTemplate = Handlebars.compile(
    readFileSync(resolve(import.meta.dirname, "iconComponent.tsx.hbs"), "utf8"),
);
const componentsIndexTemplate = Handlebars.compile(
    readFileSync(resolve(import.meta.dirname, "componentsIndex.ts.hbs"), "utf8"),
);
const indexTemplate = Handlebars.compile(readFileSync(resolve(import.meta.dirname, "index.ts.hbs"), "utf8"));

/** @type { { 16: {[iconName: string]: string}; 20: {[iconName: string]: string} } } */
const iconPaths = {
    [ICON_SIZES[0]]: {},
    [ICON_SIZES[1]]: {},
};

// parse icon paths from the generated SVG font
for (const iconSize of ICON_SIZES) {
    const iconFontSvgDocument = readFileSync(
        join(generatedSrcDir, `${iconSize}px/blueprint-icons-${iconSize}.svg`),
        "utf8",
    );

    console.info(`Parsing SVG glyphs from generated ${iconSize}px SVG icon font...`);
    parseIconGlyphs(iconFontSvgDocument, (iconName, iconPath) => {
        iconPaths[iconSize][iconName] = iconPath;
    });
    console.info(`Parsed ${Object.keys(iconPaths[iconSize]).length} ${iconSize}px icons.`);
}

// clear existing icon components
console.info("Clearing existing icon modules...");
rmSync(generatedComponentsDir, { recursive: true, force: true });

// generate an ES module for each icon
console.info("Generating ES modules for each icon...");
mkdirSync(generatedComponentsDir, { recursive: true });

for (const [iconName, icon16pxPath] of Object.entries(iconPaths[16])) {
    const icon20pxPath = iconPaths[20][iconName];
    if (icon20pxPath === undefined) {
        console.error(`Could not find corresponding 20px icon path for ${iconName}, skipping!`);
        continue;
    }
    writeFileSync(
        join(generatedComponentsDir, `${iconName}.tsx`),
        // Notes on icon component template implementation:
        //  - path "translation" transform must use "viewbox" dimensions, not "size", in order to avoid issues
        //    like https://github.com/palantir/blueprint/issues/6220
        iconComponentTemplate({
            iconName,
            icon16pxPath,
            icon20pxPath,
            pathScaleFactor: 1 / ICON_RASTER_SCALING_FACTOR,
        }),
    );
}

console.info(`Writing index file for all icon modules...`);
writeFileSync(
    join(generatedComponentsDir, "index.ts"),
    componentsIndexTemplate({
        iconNames: Object.keys(iconPaths[16]),
    }),
);

console.info(`Writing index file for package...`);
writeFileSync(
    join(generatedSrcDir, "index.ts"),
    indexTemplate({
        iconNames: Object.keys(iconPaths[16]),
    }),
);

console.info("Done.");

/**
 * Parse all icons of a given size from the SVG font generated by fantasticon.
 * At this point we've already optimized the icon SVGs through svgo (via fantasticon), so
 * we avoid duplicating that work by reading the generated glyphs here.
 *
 * @param {string} iconFontSvgDocument
 * @param {(iconName: string, iconPath: string) => void} cb iterator for each icon path
 */
function parseIconGlyphs(iconFontSvgDocument, cb) {
    const rootNode = parse(iconFontSvgDocument);
    // @ts-ignore
    const defs = rootNode.children[0].children[0];
    const glyphs = defs.children[0].children.filter(node => node.tagName === "glyph");

    for (const glyph of glyphs) {
        const name = glyph.properties["glyph-name"];

        // HACKHACK: for some reason, there are duplicates with the suffix "-1", so we ignore those
        if (name.endsWith("-1")) {
            continue;
        }

        const path = glyph.properties["d"];
        cb(name, path);
    }
}
